#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include "stm32f4xx.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "debug.h"
#include "esp_at.h"

#define     DEBUG_AT    0

#define     STM_ESP_TX   GPIO_Pin_2
#define     STM_ESP_RX   GPIO_Pin_3
#define     STM_ESP_PORT USART2

typedef enum
{
    AT_ACK_NONE,
    AT_ACK_OK,
    AT_ACK_ERR,
    AT_ACK_BUSY,
    AT_ACK_READY
} at_ack_t;

typedef struct
{
    at_ack_t ack;
    const char* string;
} at_meche_t;

static const at_meche_t at_meches[] = 
{
    {AT_ACK_OK, "OK\r\n"},
    {AT_ACK_ERR, "ERROR\r\n"},
    {AT_ACK_BUSY, "busy po..\r\n"},
    {AT_ACK_READY, "ready\r\n"}
};

static char *rxline;
static char rxbuf[1024];
static uint32_t rxlen;
static at_ack_t rx_ack;

static SemaphoreHandle_t rx_binary;

static void esp_at_io_init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    memset(&GPIO_InitStructure, 0, sizeof(GPIO_InitTypeDef));

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_Pin = STM_ESP_TX | STM_ESP_RX; 
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure); 
}

static void esp_at_irq_init(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
    NVIC_InitTypeDef NVIC_InitStructure;
    memset(&NVIC_InitStructure, 0, sizeof(NVIC_InitTypeDef));
    NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 6;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_Init(&NVIC_InitStructure);
}

static void esp_at_dma_init(void)
{
    DMA_InitTypeDef DMA_InitStruct;
    DMA_StructInit(&DMA_InitStruct);
    DMA_InitStruct.DMA_Channel = DMA_Channel_4;
    DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&USART2->DR;
    DMA_InitStruct.DMA_DIR = DMA_DIR_MemoryToPeripheral;
    DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStruct.DMA_Priority = DMA_Priority_Medium;
    DMA_InitStruct.DMA_FIFOMode = DMA_FIFOMode_Enable;
    DMA_InitStruct.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
    DMA_InitStruct.DMA_MemoryBurst = DMA_MemoryBurst_INC8;
    DMA_InitStruct.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
    DMA_Init(DMA1_Stream6, &DMA_InitStruct);
}

static void esp_at_uart_init(void)
{
    USART_InitTypeDef USART_InitStructure;
    memset(&USART_InitStructure, 0, sizeof(USART_InitTypeDef));
    USART_InitStructure.USART_BaudRate = 115200;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;

    GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_USART2); 
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_USART2);
    USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
    USART_DMACmd(USART2, USART_DMAReq_Tx, ENABLE);
    USART_Init(STM_ESP_PORT, &USART_InitStructure); 
    USART_Cmd(STM_ESP_PORT, ENABLE);
}

static void esp_at_lowlever_init(void) 
{
    esp_at_uart_init();
    esp_at_dma_init();
    esp_at_irq_init();
    esp_at_io_init();
}

static at_ack_t match_internal_ack(const char* str)
{
    for (uint8_t i = 0 ; i < sizeof(at_meches) / sizeof(at_meches[0]) ; i++)
    {
        if (strcmp(str, at_meches[i].string) == 0)
        {
            return at_meches[i].ack;
        }
    }

    return AT_ACK_NONE;
}


static at_ack_t esp_at_wait_receive(uint32_t time_out)
{
    rxlen = 0;
    rxline = rxbuf;
    if (xSemaphoreTake(rx_binary, pdMS_TO_TICKS(time_out)) == pdPASS)
        return rx_ack;
    else
        return AT_ACK_NONE;
}

static bool esp_at_wait_ready(uint32_t time_out)
{
    return esp_at_wait_receive(time_out) == AT_ACK_READY;
}

static void esp_at_usart_send(const char* cmd)
{
    uint32_t len = strlen(cmd);

    DMA1_Stream6->M0AR = (uint32_t)cmd;
    DMA1_Stream6->NDTR = len;

    DMA_ClearFlag(DMA1_Stream6, DMA_FLAG_TCIF6);
    DMA_Cmd(DMA1_Stream6, ENABLE);
}

bool esp_at_write_command(const char* cmd, uint32_t time_out)
{
#if DEBUG_AT
    printf("command: %s\r\n", cmd);
#endif

    esp_at_usart_send(cmd);
    at_ack_t ack = esp_at_wait_receive(time_out); 

#if DEBUG_AT
    printf("response: \n%s\n", rxbuf);
#endif

    return ack == AT_ACK_OK;
}

static bool esp_at_wait_bool(uint32_t timeout)
{
    for (uint32_t i = 0; i < timeout; i+=100)
    {
        if (esp_at_write_command("AT\r\n", 100))
        {
            return true;
        }
    }

    return false;
}

bool esp_at_init(void) 
{
    rx_binary = xSemaphoreCreateBinary();
    configASSERT(rx_binary);

    esp_at_lowlever_init();
    
    if (!esp_at_wait_bool(3000))
        return false;
    if(!esp_at_write_command("AT+RESTORE\r\n", 2000))
        return false;
    if(!esp_at_wait_ready(5000))
        return false;
    
    return true;
}

const char* esp_get_response(void)
{
    return rxbuf;
}

bool esp_wifi_init(void)
{
    return esp_at_write_command("AT+CWMODE=1\r\n", 2000);
}

bool esp_wifi_connect(const char* ssid, const char* pwd, const char* mac)
{
    if(ssid == NULL || pwd == NULL) // ָ����
        return false;
    char cmd[128];
    int len = snprintf(cmd, sizeof(cmd), "AT+CWJAP=\"%s\",\"%s\"\r\n", ssid, pwd);
    if(mac)
    {
        snprintf(cmd+len, sizeof(cmd), ",\"%s\"", mac);
    }

    return esp_at_write_command(cmd, 5000);
}

bool esp_at_sntp_init(void)
{
    if(!esp_at_write_command("AT+CIPSNTPCFG=1,8\r\n", 2000))
        return false;

    return true;
}

static bool parse_cwstate_response(const char* response, esp_wifi_info_t* info)
{
//    AT+CWSTATE?
//    +CWSTATE:2,"Xiaomi Mi MIX 3_5577"

//    OK
    response = strstr(response, "+CWSTATE:");
    if (response == NULL)
        return false;
    int wifi_state;
    if (sscanf(response, "+CWSTATE:%d,\"%63[^\"]", &wifi_state, info->ssid) != 2)
        return false;
    
    info->connected = (wifi_state == 2);
    return true;
}

static bool parse_cwjap_response(const char* response, esp_wifi_info_t* info)
{
//    AT+CWJAP?
//    +CWJAP:"Xiaomi Mi MIX 3_5577","da:b5:3a:e3:2f:60",9,-48,0,1,3,0,1

//    OK
    response = strstr(response, "+CWJAP:");
    if (response == NULL)
        return false;
    if (sscanf(response, "+CWJAP:\"%63[^\"]\",\"%17[^\"]\",%d,%d", info->ssid, info->bssid, &info->channel, &info->rssi) != 4)
        return false;
    
    return true;
}

static uint8_t weekday_str_to_num(const char* weekday_str)
{
    const char* weekdays[] = {"Mon", "Tue", "wed", "Thu", "Fri", "Sat", "Sun"};
    for (uint8_t i = 0 ; i< 7 ; i++)
    {
        if(strcmp(weekday_str, weekdays[i]) == 0)
        {
            return i + 1;
        }
    }
    return 0;
}

static uint8_t month_str_to_num(const char* month_str)
{
    const char* months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", 
		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
    for (uint8_t i = 0 ; i < 12 ; i++)
    {
        if (strcmp(month_str, months[i]) == 0)
        {
            return i + 1;
        }
    }
    return 0;
}

static bool parse_cipsntptime_response(const char* response, esp_date_time_t* date)
{
//	AT+CIPSNTPTIME?
//	+CIPSNTPTIME:Sun Jul 27 14:07:19 2025
//	OK
    char weekday_str[8];
    char month_str[4];
    response = strstr(response, "+CIPSNTPTIME:");
    if (sscanf(response, "+CIPSNTPTIME:%3s %3s %hhu %hhu:%hhu:%hhu %hu", 
               weekday_str, month_str, &date->day, &date->hour, &date->minute, &date->second, &date->year) != 7)
        return false;
    
    date->weekday = weekday_str_to_num(weekday_str);
    date->month = month_str_to_num(month_str);

    return true;
}

bool parse_seniverse_response(const char* response, weather_info_t* weather)
{
    response = strstr(response, "\"results\":");
    if (response == NULL)
        return false;
    
    const char* location_response = strstr(response, "\"location\":");
    if(location_response == NULL)
        return false;
    
    const char* location_name_response = strstr(location_response, "\"name\":");
    if(location_name_response)
        sscanf(location_name_response, "\"name\": \"%31[^\"]\"", weather->city);
    
    const char* location_path_response = strstr(location_response, "\"path\":");
    if(location_path_response)
        sscanf(location_path_response, "\"path\": \"%128[^\"]\"", weather->location);
    
    const char* now_response = strstr(response, "\"now\":");
    if(now_response == NULL)
        return false;
    
    const char* now_text_response = strstr(now_response, "\"text\":");
    if(now_text_response)
        sscanf(now_text_response, "\"text\": \"%15[^\"]\"", weather->weather);
    
    const char* now_code_response = strstr(now_response, "\"code\":");
    if(now_code_response)
        sscanf(now_code_response, "\"code\": \"%d\"", &weather->weather_code);

    char temperature_str[16] = {0};
    const char* now_temperature_response = strstr(now_response, "\"temperature\":");
    if(now_temperature_response)
    {
        if(sscanf(now_temperature_response, "\"temperature\": \"%15[^\"]\"", temperature_str) == 1)
        {
            weather->temperature = atof(temperature_str);
        }
    }

    return true;
}

bool esp_at_get_wifi_info(esp_wifi_info_t* info)
{
    if (!esp_at_write_command("AT+CWSTATE?\r\n", 2000))
        return false;
    if (!parse_cwstate_response(esp_get_response(), info))
        return false;
    if (!esp_at_write_command("AT+CWJAP?\r\n", 2000))
        return false;
    if (!parse_cwjap_response(esp_get_response(), info))
        return false;
    
    return true;
}

bool esp_at_get_date_time(esp_date_time_t* date)
{
    if (!esp_at_write_command("AT+CIPSNTPTIME?\r\n", 2000))
        return false;
    if (!parse_cipsntptime_response(esp_get_response(), date))
        return false;
    
    return true;
}

const char* esp_at_http_get(const char* url)
{
    char* txbuf = rxbuf;
    snprintf(txbuf, sizeof(rxbuf), "AT+HTTPCLIENT=2,1,\"%s\",,,2\r\n", url);
    bool ret = esp_at_write_command(txbuf, 5000);
    return ret ? esp_get_response() : NULL;
}

void USART2_IRQHandler(void)
{
    if (USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
    {
        if (rxlen < sizeof(rxbuf) - 1)
        {
            rxbuf[rxlen++] = USART_ReceiveData(USART2);
            if (rxbuf[rxlen - 1] == '\n')
            {
                rxbuf[rxlen] = '\0';
                at_ack_t ack = match_internal_ack(rxline);
                if (ack != AT_ACK_NONE)
                {
                    rx_ack = ack;
                    BaseType_t pxHigherPriorityTaskWoken;
                    xSemaphoreGiveFromISR(rx_binary, &pxHigherPriorityTaskWoken);
                    portYIELD_FROM_ISR(pxHigherPriorityTaskWoken);
                }
                rxline = rxbuf + rxlen;
            }
        }
        USART_ClearITPendingBit(USART2, USART_IT_RXNE);
    }
}

bool esp_wait_ready(void)
{
    if (!esp_at_init())
    {
        return false;
    }
    return true;
}

